﻿// Partial Copyright (c) LogoUI Software Solutions LTD
// Author: Vladislav Spivak
// This source file is the part of LogoFX Framework http://logofx.codeplex.com
// See accompanying licences and credits.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using LogoFX.Core;

namespace Caliburn.Micro
{
    /// <summary>
    /// Bootstrapper that uses simple container
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SimpleBootstrapper<T> : BootstrapperBase<T>
    {
        private SimpleContainer _container;
        private object _defaultLifetimeScope;

        /// <summary>
        /// Configures this instance.
        /// </summary>
        protected override void Configure()
        {
            base.Configure();

            _container = new SimpleContainer();
            _container.RegisterSingleton(typeof(IWindowManager), null, typeof(WindowManager));
            _container.RegisterSingleton(typeof(IEventAggregator), null, typeof(EventAggregator));
            _container.RegisterSingleton(typeof(T), null, typeof(T));
            _container.RegisterInstance(typeof(SimpleContainer), null, _container);

            RegisterModules();

            //comment this if you not want views and viewmodels registration
            RegisterViewsAndViewModels();

            OnConfigure(_container);
        }

        /// <summary>
        /// Called on configure.
        /// </summary>
        /// <param name="container">The container.</param>
        protected virtual void OnConfigure(SimpleContainer container)
        {
        }

        /// <summary>
        /// Gets the current lifetime scope.
        /// </summary>
        /// <value>The current lifetime scope.</value>
        protected virtual object CurrentLifetimeScope
        {
            get { return _defaultLifetimeScope ?? (_defaultLifetimeScope = new Object()); }
        }


        #region Overrides
        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation.
        /// </summary>
        /// <param name="service">The service to locate.</param>
        /// <param name="key">The key to locate.</param>
        /// <returns>The located service.</returns>
        protected override object GetInstance(Type service, string key)
        {
            return _container.GetInstance(service, key);
        }


        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation
        /// </summary>
        /// <param name="service">The service to locate.</param>
        /// <returns>The located services.</returns>
        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return _container.GetAllInstances(service);
        }

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation.
        /// </summary>
        /// <param name="instance">The instance to perform injection on.</param>
        protected override void BuildUp(object instance)
        {
            _container.BuildUp(instance);
        }

        /// <summary>
        /// Override to tell the framework where to find assemblies to inspect for views, etc.
        /// </summary>
        /// <returns>A list of assemblies to inspect.</returns>
        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            if (!Directory.Exists(ModulesPath))
                Directory.CreateDirectory(ModulesPath);

            CompositionHost.Initialize(new DirectoryCatalog(ModulesPath));
            CompositionInitializer.SatisfyImports(this);

            IEnumerable<Assembly> local = 
                GetType().Assembly
                .GetReferencedAssemblies()
                .Select(Assembly.Load)
                .Concat(new[] { GetType().Assembly })
                .Distinct();

            if(Modules!=null)
                return local.Concat(Modules.Select(a => a.GetType().Assembly)).Distinct();

            return local;
        }

        #endregion

        /// <summary>
        /// Gets or sets the modules.
        /// </summary>
        /// <value>The modules.</value>
        [ImportMany(typeof(ISimpleModule))]
        public ISimpleModule[] Modules { get; set; }

        /// <summary>
        /// Gets the modules path that MEF need to search.
        /// </summary>
        /// <value>The modules path.</value>
        public virtual string ModulesPath
        {
            get { return "."; }
        }

        private void RegisterModules()
        {
            if (Modules != null)
                Modules.Apply(a=>a.RegisterModule(_container,()=>CurrentLifetimeScope));
        }


        #region private implementation
        private void RegisterViewsAndViewModels()
        {
            //  register view models
            AssemblySource.Instance.ToArray()
              .SelectMany(ass => ass.GetTypes())
                //  must be a type that ends with ViewModel
              .Where(type => type != typeof(T) && type.Name.EndsWith("ViewModel"))
                //  must be in a namespace ending with ViewModels
              .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace!=null && type.Namespace.EndsWith("ViewModels"))
                //  must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this)
              .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name, false) != null)
                //  registered as self
              .Apply(a => _container.RegisterPerRequest(a, null, a));

            //  register views
            AssemblySource.Instance.ToArray()
              .SelectMany(ass => ass.GetTypes())
                //  must be a type that ends with View
              .Where(type => type.Name.EndsWith("View"))
                //  must be in a namespace ending with ViewModels
              .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace != null && type.Namespace.EndsWith("Views"))
                //  registered as self
              .Apply(a => _container.RegisterPerRequest(a, null, a));
            
        }
        #endregion
    }
}
